package com.atguigu.aclservice.controller;
import com.alibaba.fastjson.JSON;
import com.atguigu.commonutils.R;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * http接口post请求幂等性校验
 * <p>
 * 1、
 * <p/>
 *
 * @author jhj  Date: 2025/8/29 10:48 Version: v 0.1
 */
@Slf4j
@Aspect
@Component
public class IdempotentAspect {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Pointcut("@annotation(idempotent)")
    public void pointcut(Idempotent idempotent) {
    }

    @Around(value = "pointcut(idempotent)", argNames = "joinPoint,idempotent")
    public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
        String key = generateKey(joinPoint);
        long expire = idempotent.expireSeconds();
        Boolean success = null;
        try {
            // 执行幂等性检查
            success = redisTemplate.opsForValue().setIfAbsent(key, "1");
            log.info("幂等性校验，key:{}, success:{}", key, success);

            if (Boolean.TRUE.equals(success)) {
                // 设置过期时间，避免 Redis 锁被长期占用
                redisTemplate.expire(key, expire, TimeUnit.SECONDS);
            } else {
                handleResponse();
                return null;
            }
            // 执行目标方法
            return joinPoint.proceed();
        } catch (Exception e) {
            log.error("幂等性检查时发生异常，key:{}", key, e);
            return null;
        }
    }

    // 生成幂等 key，基于方法名 + 参数哈希
    private String generateKey(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String methodName = method.getDeclaringClass().getName() + "." + method.getName();

        Object[] args = joinPoint.getArgs();
        String paramString = Arrays.stream(args)
                .map(arg -> arg == null ? "null" : arg.toString())
                .collect(Collectors.joining(","));

        String rawKey = methodName + ":" + paramString;
        return "idem:" + DigestUtils.md5DigestAsHex(rawKey.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 处理失败响应
     *
     * @throws IOException 异常
     */
    private void handleResponse() throws IOException {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (!(requestAttributes instanceof ServletRequestAttributes)) {
            throw new IllegalStateException("Current request is not a servlet request");
        }

        HttpServletResponse response = ((ServletRequestAttributes) requestAttributes).getResponse();
        if (response == null || response.isCommitted()) {
            return;
        }

        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());

        try (PrintWriter out = response.getWriter()) {
            R result = buildErrorResult();
            out.print(JSON.toJSONString(result));
        }
    }

    private R buildErrorResult() {
        return R.error().message("幂等性校验失败"  );
    }
}